#region Using References

using System;
using System.Diagnostics.Contracts;
using Beaker.OpenCube.Game.Converters;
using Beaker.OpenCube.Mapping;
using Beaker.OpenCube.Nbt;

#endregion

namespace Beaker.OpenCube.Game
{
	public class EntityData
		: ObservableExpandoObject
	{
		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( _airDuration >= TimeSpan.Zero );
			Contract.Invariant( _airDuration <= MaxAirDuration );
			Contract.Invariant( _portalCooldown >= TimeSpan.Zero );
		}

		private string _id;

		/// <summary>
		/// Gets or sets the id of the entity.
		/// </summary>
		/// <value>
		/// The id of the entity.
		/// </value>
		[Mapping( Name = "id" )]
		public virtual string Id
		{
			get
			{
				Contract.Ensures( Contract.Result<string>() == _id );
				return _id;
			}
			set
			{
				Contract.Ensures( _id == value );
				SetValue( ref _id, value );
			}
		}

		private Coordinates3<double> _position;

		/// <summary>
		/// Gets or sets the current position of the entity.
		/// </summary>
		/// <value>
		/// The current position of the entity.
		/// </value>
		[Mapping( Tag = NbtTag.List, ListTag = NbtTag.Double, ListFixedSize = 3, Name = "Pos", Converter = typeof( Coordinates3Converter<double> ) )]
		public Coordinates3<double> Position
		{
			get
			{
				Contract.Ensures( Contract.Result<Coordinates3<double>>().Equals( _position ) );
				return _position;
			}
			set
			{
				Contract.Ensures( _position.Equals( value ) );
				SetValue( ref _position, value );
			}
		}

		private Coordinates3<double> _motion;

		/// <summary>
		/// Gets or sets the motion of the entity in meters per tick.
		/// </summary>
		/// <value>
		/// The motion of the entity in meters per tick.
		/// </value>
		[Mapping( Tag = NbtTag.List, ListTag = NbtTag.Double, ListFixedSize = 3, Name = "Motion", Converter = typeof( Coordinates3Converter<double> ) )]
		public Coordinates3<double> Motion
		{
			get
			{
				Contract.Ensures( Contract.Result<Coordinates3<double>>().Equals( _motion ) );
				return _motion;
			}
			set
			{
				Contract.Ensures( _motion.Equals( value ) );
				SetValue( ref _motion, value );
			}
		}

		private Rotation _rotation;

		/// <summary>
		/// Gets or sets the rotation of the entity in degrees.
		/// </summary>
		/// <value>
		/// The rotation of the entity in degrees.
		/// </value>
		[Mapping( Tag = NbtTag.List, ListTag = NbtTag.Float, Converter = typeof( RotationConverter ) )]
		public Rotation Rotation
		{
			get
			{
				Contract.Ensures( Contract.Result<Rotation>().Equals( _rotation ) );
				return _rotation;
			}
			set
			{
				Contract.Ensures( _rotation.Equals( value ) );
				SetValue( ref _rotation, value );
			}
		}

		private float _fallDistance;

		/// <summary>
		/// Gets or sets the distance the entity has fallen.
		/// </summary>
		/// <value>
		/// The distance the entity has fallen.
		/// </value>
		public float FallDistance
		{
			get
			{
				Contract.Ensures( Contract.Result<float>().Equals( _fallDistance ) );
				return _fallDistance;
			}
			set
			{
				Contract.Ensures( _fallDistance.Equals( value ) );
				SetValue( ref _fallDistance, value );
			}
		}

		private TimeSpan _fireDuration;

		/// <summary>
		/// Gets or sets the time until the fire is put out
		/// </summary>
		/// <value>
		/// The time until the fire is put out.
		/// </value>
		/// <remarks>
		/// Negative values reflect how long the entity can stand in fire before burning.
		/// </remarks>
		[Mapping( Name = "Fire", Converter = typeof( GameTicksTimeSpanConverter ) )]
		public TimeSpan FireDuration
		{
			get
			{
				Contract.Ensures( Contract.Result<TimeSpan>() == _fireDuration );
				return _fireDuration;
			}
			set
			{
				Contract.Ensures( _fireDuration == value );
				SetValue( ref _fireDuration, value );
			}
		}

		public static readonly TimeSpan MaxAirDuration = TimeSpanExtensions.FromGameTicks( 300 );
		private TimeSpan _airDuration;

		/// <summary>
		/// Gets or sets the duration before the entity runs out of air.
		/// </summary>
		/// <value>
		/// The duration of the air.duration before the entity runs out of air.
		/// </value>
		[Mapping( Name = "Air", Converter = typeof( GameTicksTimeSpanConverter ) )]
		public TimeSpan AirDuration
		{
			get
			{
				Contract.Ensures( Contract.Result<TimeSpan>() >= TimeSpan.Zero );
				Contract.Ensures( Contract.Result<TimeSpan>() <= MaxAirDuration );
				Contract.Ensures( Contract.Result<TimeSpan>() == _airDuration );

				return _airDuration;
			}
			set
			{
				Contract.Requires<ArgumentOutOfRangeException>( value >= TimeSpan.Zero, "AirDuration must be non negative" );
				Contract.Requires<ArgumentOutOfRangeException>( value <= MaxAirDuration, "AirDuration cannot be above 300 ticks" );
				Contract.Ensures( _airDuration == value );

				_airDuration = value;
			}
		}

		private bool _isOnGround;

		/// <summary>
		/// Gets or sets a value indicating whether this entity is touching the ground.
		/// </summary>
		/// <value>
		/// <c>true</c> if this entity is touching ground; otherwise, <c>false</c>.
		/// </value>
		[Mapping( Name = "OnGround" )]
		public bool IsOnGround
		{
			get
			{
				Contract.Ensures( Contract.Result<bool>() == _isOnGround );
				return _isOnGround;
			}
			set
			{
				Contract.Ensures( _isOnGround == value );
				SetValue( ref _isOnGround, value );
			}
		}

		private bool _isInvulnerable;

		/// <summary>
		/// Gets or sets a value indicating whether this entity is invulnerable.
		/// </summary>
		/// <value>
		/// <c>true</c> if this entity is invulnerable; otherwise, <c>false</c>.
		/// </value>
		/// <remarks>
		///  This applies to living and nonliving entities alike: mobs will not take damage from any source (including potion effects) 
		/// and objects such as vehicles and item frames cannot be destroyed unless their supports are removed. 
		/// Note that these entities also cannot be moved by fishing rods, attacks, explosions, or projectiles.
		/// </remarks>
		[Mapping( Name = "Invulnerable" )]
		public bool IsInvulnerable
		{
			get
			{
				Contract.Ensures( Contract.Result<bool>() == _isInvulnerable );
				return _isInvulnerable;
			}
			set
			{
				Contract.Ensures( _isInvulnerable == value );
				SetValue( ref _isInvulnerable, value );
			}
		}

		private TimeSpan _portalCooldown;

		/// <summary>
		/// Gets or sets the time before which the entity may be teleported back through a portal of any kind.
		/// </summary>
		/// <value>
		/// The time before which the entity may be teleported back through a portal of any kind.
		/// </value>
		[Mapping( IsOptional = true, Converter = typeof( GameTicksTimeSpanConverter ) )]
		public TimeSpan PortalCooldown
		{
			get
			{
				Contract.Ensures( Contract.Result<TimeSpan>() >= TimeSpan.Zero );
				Contract.Ensures( Contract.Result<TimeSpan>() == _portalCooldown );
				return _portalCooldown;
			}
			set
			{
				Contract.Requires<ArgumentOutOfRangeException>( value >= TimeSpan.Zero, "PortalCooldown value must be non negative" );
				Contract.Ensures( _portalCooldown == value );
				SetValue( ref _portalCooldown, value );
			}
		}

		private string _customName;

		/// <summary>
		/// Gets or sets the custom name of this entity.
		/// </summary>
		/// <value>
		/// The custom name of this entity.
		/// </value>
		/// <remarks>
		/// Appears in player death messages and villager trading interfaces. May not exist. 
		/// Not saved for entities that aren't Mobs, but loaded and affects the name of the dropped item for most entities 
		/// (e.g. affects the name of the minecart dropped by MinecartRideable, MinecartChest, MinecartFurnace).
		/// </remarks>
		[Mapping( IsOptional = true )]
		public string CustomName
		{
			get
			{
				Contract.Ensures( Contract.Result<string>() == _customName );
				return _customName;
			}
			set
			{
				Contract.Ensures( _customName == value );
				SetValue( ref _customName, value );
			}
		}

		private Guid _uniqueId;

		/// <summary>
		/// Gets or sets the unique id (<see cref="Guid"/>) of this entity.
		/// </summary>
		/// <value>
		/// The unique id (<see cref="Guid"/>) of this entity.
		/// </value>
		[Mapping( Names = new[] { "UUIDLeast", "UUIDMost" }, IsOptional = true, Converter = typeof( UuidGuidConverter ) )]
		public Guid UniqueId
		{
			get
			{
				Contract.Ensures( Contract.Result<Guid>() == _uniqueId );
				return _uniqueId;
			}
			set
			{
				Contract.Ensures( _uniqueId == value );
				SetValue( ref _uniqueId, value );
			}
		}
	}
}